﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;

namespace WpfApp5_StackPanel
{
    public class UndoService
    {
        Stack<UndoOperation> undoStack = new Stack<UndoOperation>();
        Stack<UndoOperation> redoStack = new Stack<UndoOperation>();

        #region Attached Properties
        public static readonly DependencyProperty SharedUndoRedoScopeProperty = DependencyProperty.RegisterAttached(
            "SharedUndoRedoScope",
            typeof(UndoService),
            typeof(UndoService),
            new FrameworkPropertyMetadata(
                null,
                FrameworkPropertyMetadataOptions.Inherits,
                new PropertyChangedCallback(OnUseGlobalUndoRedoScopeChanged)
                )
            );

        private static void OnUseGlobalUndoRedoScopeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            if (d is TextBoxBase)
            {
                if (e.OldValue != null)
                {
                    RemoveEventHandlers(d as TextBoxBase, e.OldValue as UndoService);
                }
                if (e.NewValue != null)
                {
                    AttachEventHandlers(d as TextBoxBase, e.NewValue as UndoService);
                }
            }
        }

        private static void AttachEventHandlers(TextBoxBase textBox, UndoService service)
        {
            if (textBox != null && service != null)
            {
                textBox.AddHandler(
                    CommandManager.PreviewExecutedEvent,
                    new ExecutedRoutedEventHandler(service.ExecuteHandler),
                    true);
                textBox.TextChanged += new TextChangedEventHandler(service.TextChangedHandler);
            }
        }

        private static void RemoveEventHandlers(TextBoxBase textBox, UndoService service)
        {
            if (textBox != null && service != null)
            {
                textBox.RemoveHandler(
                    CommandManager.PreviewExecutedEvent,
                    new ExecutedRoutedEventHandler(service.ExecuteHandler));
                textBox.TextChanged -= new TextChangedEventHandler(service.TextChangedHandler);
            }
        }

        #endregion

        private void TextChangedHandler(object sender, TextChangedEventArgs e)
        {
            this.AddUndoableAction(sender as TextBoxBase, e.UndoAction);
        }

        private void AddUndoableAction(TextBoxBase sender, UndoAction action)
        {
            if (action == UndoAction.Undo)
            {
                redoStack.Push(new UndoOperation(sender, action));
            }
            else
            {
                if (undoStack.Count > 0)
                {
                    UndoOperation op = undoStack.Peek();
                    if ( (op.Sender == sender) && (action == UndoAction.Merge))
                    {
                        // no-op
                    }
                    else
                    {
                        PushUndoOperation(sender, action);
                    }
                }
                else
                {
                    PushUndoOperation(sender, action);
                }
            }
        }

        private void PushUndoOperation(TextBoxBase sender, UndoAction action)
        {
            undoStack.Push(new UndoOperation(sender, action));
            System.Diagnostics.Debug.WriteLine("PUSHED");
        }

        private void ExecuteHandler(object sender, ExecutedRoutedEventArgs e)
        {
            if (e.Command == ApplicationCommands.Undo)
            {
                e.Handled = true;
                Undo();
            }
            if (e.Command == ApplicationCommands.Redo)
            {
                e.Handled = true;
                Redo();
            }
        }

        private void Redo()
        {
            if (redoStack.Count > 0)
            {
                UndoOperation op = redoStack.Pop();
                op.Sender.Redo();
                op.Sender.Focus();
            }
        }

        private void Undo()
        {
            if (undoStack.Count > 0)
            {
                UndoOperation op = undoStack.Pop();
                op.Sender.Undo();
                op.Sender.Focus();
            }
        }

        public static void SetSharedUndoRedoScope(DependencyObject depObj, bool isSet)
        {
            depObj.SetValue(SharedUndoRedoScopeProperty, isSet);
        }

        public static UndoService GetSharedUndoRedoScope(DependencyObject depObj)
        {
            return depObj.GetValue(SharedUndoRedoScopeProperty) as UndoService;
        }

    }
}
